import { ElMessage } from 'element-plus';
import { ContentTypeEnum, type IResponseData, type IAxiosRequestConfig, type IHttpCodeMessage, ResultTypeEnum } from '@/types/http.d';

import axios, { type AxiosInstance, type AxiosPromise, type AxiosResponse, type Canceler, type RawAxiosRequestHeaders } from 'axios';

const httpCodeMessage: IHttpCodeMessage = {
  '200': 'request success',
  '400': 'bad request',
  '401': 'Unauthorized',
  '403': 'Forbidden',
  '404': 'Not Found',
  '500': 'Internal Server Error',
  '502': 'Bad Gateway',
  '503': 'Service Unavailable',
  '504': 'Gateway Timeout',
};

let requestPendingMap = new Map<string, Canceler>();

/**
 * 生成唯一请求标识URL
 * @param config axios配置
 * @returns string 唯一标识
 */
export const getRequestPendingUrl = (config: IAxiosRequestConfig) => {
  const urlArray = [config.method, config.url, config.noRepeatId];
  if (config.noRepeatId) {
    urlArray.push(config.noRepeatId);
  }
  return urlArray.join('&');
};

/**
 * 自定义请求消除器类
 */
export class DwHttpCanceler {
  /**
   * 添加请求
   * @param config axios配置
   * @returns void
   */
  addPending(config: IAxiosRequestConfig) {
    if (config.noRepeatId) {
      //在请求开始钱，对之前的请求做检查取消操作
      this.removePending(config);
      const url = getRequestPendingUrl(config);
      config.cancelToken =
        config.cancelToken ||
        new axios.CancelToken((cancel) => {
          //如果pending中不存在的当前请求，则添加进去
          if (!requestPendingMap.has(url)) {
            requestPendingMap.set(url, cancel);
          }
        });
    }
  }
  /**
   * 移除请求
   * @param config axios配置
   * @returns void
   */
  removePending(config: IAxiosRequestConfig) {
    const url = getRequestPendingUrl(config);
    if (requestPendingMap.has(url)) {
      //如果在pending中存在当前请求标识，需要取消当前请求，并且移除
      const cancel = requestPendingMap.get(url);
      if (cancel && typeof cancel === 'function') {
        cancel();
      }

      requestPendingMap.delete(url);
    }
  }
  /**
   * @description 清空所有pending
   * @returns void
   */
  removeAllPending(): void {
    requestPendingMap.forEach((cancel) => {
      if (cancel && typeof cancel === 'function') {
        cancel();
      }
    });
    requestPendingMap.clear();
  }
  /**
   * 重置
   * @returns void
   */
  reset(): void {
    requestPendingMap = new Map<string, Canceler>();
  }
}

export const requestCanceler = new DwHttpCanceler();

/**
 * 自定义请求类
 */
export class DwHttp {
  request: AxiosInstance;
  /**
   * 构造函数
   * @param config axios配置
   */
  public constructor(config: IAxiosRequestConfig) {
    // 初始化传参，所以目前 headers 是 RawAxiosRequestHeaders | undefined 类型。
    const { contentType, headers = {}, ...otherCofing } = config || {};
    this.request = axios.create({
      ...otherCofing,
      headers: {
        ...(headers as RawAxiosRequestHeaders),
        'content-type': contentType || ContentTypeEnum.JSON,
      },
    });
    this.request.interceptors.request.use(
      (axiosConfig) => {
        //将当前请求添加到消除器 pending 中
        requestCanceler.addPending(axiosConfig);
        return axiosConfig;
      },
      (error) => {
        return this.errorHandler(error);
      }, //已在export default catch
    );
    this.request.interceptors.response.use(
      (response: AxiosResponse<IResponseData>) => {
        //在请求结束后，移除本次请求
        requestCanceler.removePending(response.config);
        this.customMessageTip(response);
        return response;
      },
      (error) => {
        return this.errorHandler(error);
      },
    );
  }
  all<T = unknown, R = AxiosResponse<T>>(config: IAxiosRequestConfig): AxiosPromise<R> {
    const { contentType, headers = {}, ...otherCofing } = config || {};
    return this.request({
      ...otherCofing,
      headers: {
        ...(headers as RawAxiosRequestHeaders),
        'content-type': contentType || ContentTypeEnum.JSON,
      },
    })
      .then((response: AxiosResponse) => response.data)
      .catch((error: unknown) => this.errorHandler(error));
  }

  get<T = unknown, R = AxiosResponse<T>>(config: IAxiosRequestConfig): AxiosPromise<R> {
    const { contentType, headers = {}, ...otherCofing } = config || {};
    return this.request({
      ...otherCofing,
      method: 'get',
      headers: {
        ...(headers as RawAxiosRequestHeaders),
        'content-type': contentType || ContentTypeEnum.JSON,
      },
    })
      .then((response: AxiosResponse) => response.data)
      .catch((error: unknown) => this.errorHandler(error));
  }

  post<T = unknown, R = AxiosResponse<T>>(config: IAxiosRequestConfig): AxiosPromise<R> {
    const { contentType, headers = {}, ...otherCofing } = config || {};
    return this.request({
      ...otherCofing,
      method: 'post',
      headers: {
        ...(headers as RawAxiosRequestHeaders),
        'content-type': contentType || ContentTypeEnum.JSON,
      },
    })
      .then((response: AxiosResponse) => response.data)
      .catch((error: unknown) => this.errorHandler(error));
  }

  /**
   * 自定义错误消息提示
   * @param response axios 返回的Response
   * @param successTip 请求成功是否提示
   * @param errorTip 请求失败是否提示
   */
  private customMessageTip(response: AxiosResponse<IResponseData, IAxiosRequestConfig>): void {
    let message: string;
    //这里因为扩展了属性
    const axiosConfig = response.config as IAxiosRequestConfig;
    if (response.data.type === ResultTypeEnum.SUCCESS && axiosConfig.isSuccessTip !== false) {
      //先从前端取，如果前端没有，则从后端取，如果后端没有，就用统一内置的
      message = axiosConfig.successMessage ?? response.data.message ?? 'request success';

      ElMessage({ message: message, type: 'success', plain: true });
    }
    if (response.data.type !== ResultTypeEnum.SUCCESS && axiosConfig.isErrorTip !== false) {
      //先从前端取，如果前端没有，则从后端取，如果后端没有，就用统一内置的
      message = axiosConfig.errorMessage ?? response.data.message ?? 'request exception';
      if (message.indexOf('token 无效') > -1 || message.indexOf('未能读取到有效 token') > -1) {
        if (window.location.href.indexOf('/login') > -1) {
          return;
        }
        window.location.href = '/login';
      }
      ElMessage({ message: message, type: 'error', plain: true });
    }
  }
  /**
   * errorHandler错误
   * @param error axios内部error申明的就是any，所以这里声明保持一致
   * @returns
   */

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  private errorHandler(error: any): Promise<unknown> {
    const { response, code } = error;
    if (response && response.status) {
      const errorText = httpCodeMessage[response.status] || response.statusText;
      const { status, request } = response;
      ElMessage({ message: errorText + status + request, type: 'error', plain: true });
      console.log(errorText + status + request);
    } else if (code !== 'ERR_CANCELED' && !response) {
    }
    return Promise.reject(error);
  }
}
const instance = new DwHttp({
  baseURL: '',
  withCredentials: false, // 当跨域请求时发送cookie
  timeout: 0, // 请求超时时间,5000(单位毫秒) / 0 不做限制
});

/**
 * @description: 导出 axios 方法
 * @param config IAxiosRequestConfig 请求参数
 * @returns AxiosPromise
 */
export function dwHttp<T = unknown>(config: IAxiosRequestConfig): AxiosPromise<AxiosResponse<T>> {
  return instance.all<T>(config);
}
